package se.webbzon.boltzmann.game.sequence.builder;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import se.webbzon.boltzmann.environment.AbstractEnvironment;
import se.webbzon.oschi01.worldobject.WorldObject;

public class SequenceBuilderDeflation {
	
	// The raw data of the deflated sequence builder
	private final byte[] data;
	
	// The dependencies of the deflated sequence builder
	private final WorldObject[] dependencies;
	
	/*============================================================
	Constructors
	============================================================*/
	
	public SequenceBuilderDeflation(SequenceBuilder builder) {
		int size = 12;
		final int rows = builder.rows();
		
		// Extract the number of bindings
		List<Short> hardBindings = new ArrayList<Short>(rows);
		List<Short> softBindings = new ArrayList<Short>(rows);
		for (short i = 0; i < rows; i++) {
			AbstractSequenceRow row = builder.getRow(i);
			AbstractSequenceBinding binding = row.getBinding();
			if (binding != null) {
				if (binding.isHard())
					hardBindings.add(i);
				else
					softBindings.add(i);
			}
		}
		
		// Extract hard bindings
		int bindings = hardBindings.size();
		size += 2*bindings;
		dependencies = new WorldObject[bindings];
		for (int i = 0; i < bindings; i++)
			dependencies[i] = builder.getRow(hardBindings.get(i)).getBinding().getHard();
		
		// Extract soft bindings
		bindings = softBindings.size();
		size += 4*bindings;
		byte[][] softBindingData = new byte[bindings][];
		for (int i = 0; i < bindings; i++) {
			String soft = builder.getRow(softBindings.get(i)).getBinding().getSoft();
			try {
				byte[] data = soft.getBytes("UTF-8");
				softBindingData[i] = data;
				size += data.length;
			} catch (UnsupportedEncodingException e) {
				softBindingData[i] = new byte[0];
			}
		}
		
		// Calculate the row data offset
		final int offset = size;
		size += 4;
		
		// Extract row data
		byte[][] names = new byte[rows][];
		byte[][] deflations = new byte[rows][];
		size += 4*rows;
		for (int i = 0; i < rows; i++) {
			AbstractSequenceRow row = builder.getRow(i);
			// Extract row name
			try {
				byte[] data = row.getName().getBytes("UTF-8");
				size += data.length;
				names[i] = data;
			} catch (UnsupportedEncodingException e) {
				names[i] = new byte[0];
			}
			// Extract row deflation
			byte[] deflation = row.getDeflation().deflate(0).array();
			deflations[i] = deflation;
			// Extract links
			int links = row.links();
			size += links*2 + deflation.length;
		}
		
		// Write the data to a byte buffer
		ByteBuffer buff = ByteBuffer.allocate(size);
		// Write header
		buff.put(new byte[] {'S', 'B'});
		buff.putShort((short) rows);
		buff.putShort((short) hardBindings.size());
		buff.putShort((short) softBindings.size());
		buff.putInt(offset);
		
		// Write hard bindings
		for (int i = 0; i < hardBindings.size(); i++)
			buff.putShort(hardBindings.get(i));
		
		// Write soft bindings
		for (int i = 0; i < softBindings.size(); i++) {
			buff.putShort(softBindings.get(i));
			byte[] data = softBindingData[i];
			buff.putShort((short) data.length);
			buff.put(data);
		}
		
		for (int i = 0; i < rows; i++) {
			// Export rows
			AbstractSequenceRow row = builder.getRow(i);
			byte[] name = names[i];
			
			// Write header
			buff.putShort((short) name.length);
			short links = (short) row.links();
			buff.putShort(links);
			
			// Write name
			buff.put(name);
			
			// Write links
			for (int j = 0; j < links; j++) {
				Integer link = row.getLink(j);
				if (link == null)
					buff.putShort((short) -1);
				else
					buff.putShort((short) ((int) link));
			}
			
			// Write body
			buff.put(deflations[i]);
		}
		
		data = buff.array();
	}
	
	public SequenceBuilderDeflation(byte[] data, WorldObject[] dependencies) {
		this.data = data;
		this.dependencies = dependencies;
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/** Inflated this sequence builder delfation into a sequence builder. **/
	public SequenceBuilder inflate(	AbstractEnvironment env, 
									SequenceRowInflatorGroup inflatorGroup) 
											throws SequenceBuilderException {
		ByteBuffer buff = ByteBuffer.wrap(data);
		byte[] recognizer = new byte[2];
		buff.get(recognizer, 0, 2);
		if (Arrays.equals(recognizer, new byte[] {'S', 'B'})) {
			// Import sequence builder from raw data
			SequenceBuilder builder = new SequenceBuilder(env);
			final short rows = buff.getShort();
			final short hard = buff.getShort();
			final short soft = buff.getShort();
			buff.getInt(); // Skip offset
			
			// Import hard bindings
			final Map<Short,HardSequenceBinding> hardBindings = 
					new HashMap<Short,HardSequenceBinding>();
			for (int i = 0; i < hard; i++) {
				HardSequenceBinding binding = new HardSequenceBinding(dependencies[i]);
				hardBindings.put(buff.getShort(),binding);
			}
			
			// Import soft bindings
			final Map<Short,SoftSequenceBinding> softBindings =
					new HashMap<Short,SoftSequenceBinding>();
			for (int i = 0; i < soft; i++) {
				short row = buff.getShort();
				byte[] bindingData = new byte[buff.getShort()];
				buff.get(bindingData, 0, bindingData.length);
				String binding;
				try {
					binding = new String(bindingData,"UTF-8");
				} catch (UnsupportedEncodingException e) {
					binding = "";
				}
				softBindings.put(row, new SoftSequenceBinding(binding));
			}
			
			// Create link storage
			Integer[][] links = new Integer[rows][];
			
			// Import rows
			for (short row = 0; row < rows; row++) {
				// Read header
				byte[] nameData = new byte[buff.getShort()];
				links[row] = new Integer[buff.getShort()];
				
				// Read name
				buff.get(nameData, 0, nameData.length);
				String name;
				try {
					name = new String(nameData,"UTF-8");
				} catch (UnsupportedEncodingException e) {
					throw new SequenceBuilderException(e);
				}
				
				// Read links
				for (int i = 0; i < links[row].length; i++) {
					short link = buff.getShort();
					if (link == -1)
						links[row][i] = null;
					else
						links[row][i] = (int) link;
				}
				
				// Create row
				AbstractSequenceRowInflator inflator = 
						inflatorGroup.getFactory(name);
				if (inflator == null)
					throw new SequenceBuilderException(	"No sequence row " +
														"inflator was found" +
														"with the name \"" + 
														name + "\".");
				SequenceRowDeflation deflation = inflator.makeDeflation();
				deflation.inflate(buff);
				SequenceRowInflatorBody body = new SequenceRowInflatorBody(builder,deflation);
				AbstractSequenceRow sequenceRow = inflator.makeNew(body);
				
				// Add bindings
				AbstractSequenceBinding binding = hardBindings.get(row);
				if (binding == null) {
					binding = softBindings.get(row);
					if (binding != null)
						sequenceRow.setBinding(binding);
				} else
					sequenceRow.setBinding(binding);
			}
			
			// Add links
			for (int row = 0; row < rows; row++)
				builder.getRow(row).setLinks(links[row]);
			
			return builder;
		} else
			return null;
	}
	
	/** Returns the raw data of the deflated sequence builder. **/
	public byte[] getData() {
		return data;
	}
	
	/** Returns the dependencies of the deflated sequence builder. **/
	public WorldObject[] getDependencies() {
		return dependencies;
	}

}
